package com.smart.thrift.config.server;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.ReflectUtil;
import com.smart.thrift.base.Invocation;
import com.smart.thrift.base.InvocationImpl;
import com.smart.thrift.base.ThriftInterceptor;
import com.smart.thrift.base.ThriftSerializer;
import com.smart.thrift.model.common.ThriftServiceSignature;
import com.smart.thrift.thriftapi.BaseException;
import com.smart.thrift.thriftapi.BaseRequest;
import com.smart.thrift.thriftapi.BaseResponse;
import org.springframework.beans.MethodInvocationException;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ApplicationContext;

import javax.el.MethodNotFoundException;
import java.beans.IndexedPropertyChangeEvent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.stream.Collectors;

public class ThriftServerInvocationHandler implements InvocationHandler {
    private final Class<?> beanClass;
    private final Class<?> ifaceClass;
    private final ThriftServiceSignature serviceSignature;
    private final ApplicationContext applicationContext;

    private Object bean;
    private List<ThriftInterceptor> interceptors;
    private ThriftSerializer thriftSerializer;


    public ThriftServerInvocationHandler(ApplicationContext applicationContext, Class<?> beanClass, Class<?> ifaceClass, ThriftServiceSignature serviceSignature) throws NoSuchMethodException {
        this.applicationContext = applicationContext;
        this.beanClass = beanClass;
        this.ifaceClass = ifaceClass;
        this.serviceSignature = serviceSignature;
    }

    private final AtomicBoolean initialized = new AtomicBoolean(false);

    private void init() {
        this.thriftSerializer = applicationContext.getBean(ThriftSerializer.class);
        String beanName = applicationContext.getBeanNamesForType(beanClass)[0];
        this.bean = applicationContext.getBean(beanName);
        this.interceptors = applicationContext.getBeansOfType(ThriftInterceptor.class).values().stream()
                .sorted((o1, o2) -> o1.getOrder().compareTo(o2.getOrder())).collect(Collectors.toList());
        initialized.set(true);
    }

    private static String findBootClassName(ApplicationContext context) {
        Map<String, Object> annotatedBeans = context.getBeansWithAnnotation(SpringBootApplication.class);
        return annotatedBeans.isEmpty() ? null : ((Supplier<String>) () -> {
            String simpleName = annotatedBeans.values().toArray()[0].getClass().getSimpleName();
            if (simpleName.contains("$")) {
                simpleName = simpleName.substring(0, simpleName.indexOf('$'));
            }
            return simpleName.replace("Application", "");
        }).get();
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] arguments) throws Throwable {
        if (!initialized.get()) {
            init();
        }
        switch (method.getName()) {
            case "toString":
                return ifaceClass.getName();
            case "equals":
                Object serviceSignature1;
                return arguments != null && arguments.length == 1 && ClassUtil.isAssignable(ifaceClass, arguments[0].getClass()) &&
                        (serviceSignature1 = ReflectUtil.getFieldValue(arguments[0], "serviceSignature")) != null && serviceSignature.equals(serviceSignature1);
            case "hashCode":
                return this.hashCode();
            case "send":
                BaseResponse baseResponse = new BaseResponse();
                if (arguments == null || arguments.length != 1 || !(arguments[0] instanceof BaseRequest)) {
                    baseResponse.setCode(-1);
                    baseResponse.setMsg("failed");
                    return baseResponse;
                }
                BaseRequest baseRequest = (BaseRequest) arguments[0];
                String methodName = baseRequest.getMethodName();
                List<String> paramTypes = baseRequest.getParamTypes();
                List<String> params = baseRequest.getParams();
                Map<String, String> header = Objects.nonNull(baseRequest.getHeader()) ? baseRequest.getHeader() : new HashMap<>();
                Class<?>[] argTypes = new Class[paramTypes.size()];
                Object[] args = new Object[paramTypes.size()];
                for (int i = 0; i < paramTypes.size(); i++) {
                    String paramType = paramTypes.get(i);
                    String arg = params.get(i);
                    try {
                        argTypes[i] = Class.forName(paramType);
                        args[i] = thriftSerializer.deserializeParameter(arg, argTypes[i]);
                    } catch (ClassNotFoundException e) {
                        throw new MethodInvocationException(new IndexedPropertyChangeEvent(methodName, "parameter class not found", paramType, null, i), e);
                    }
                }
                Method declaredMethod = ClassUtil.getDeclaredMethod(beanClass, methodName, argTypes);
                if (Objects.isNull(declaredMethod)) {
                    throw new MethodNotFoundException("no implementation method found in spring");
                }
                if (!interceptors.isEmpty()) {
                    Invocation invocation1 = new InvocationImpl(serviceSignature.getServiceName(), declaredMethod.getName(), serviceSignature.getTargetServiceClass().getName(), argTypes, args, header);
                    boolean match = interceptors.stream().anyMatch(thriftInterceptor -> thriftInterceptor.preHandle(invocation1));
                    if (!match) {
                        return null;
                    }
                }
                Object invoke = null;
                try {
                    invoke = ReflectUtil.invoke(bean, declaredMethod, args);
                } catch (UtilException e) {
                    BaseException baseException = new BaseException();
                    baseException.setCode(-1);
                    baseException.setMessage(ExceptionUtil.stacktraceToString(e));
                    baseException.setDatetime(DateUtil.current());
                    throw baseException;
                }
                for (ThriftInterceptor interceptor : interceptors) {
                    invoke = interceptor.postHandle(invoke);
                }
                baseResponse.setCode(1);
                baseResponse.setMsg("success");
                if (Objects.nonNull(invoke)) {
                    baseResponse.setDataType(invoke.getClass().getName());
                    baseResponse.setData(thriftSerializer.serializeReturnValue(invoke));
                }
                return baseResponse;
        }
        return null;
    }


}
